package com.fognl.core.net.handler;

import com.fognl.core.net.handler.Request.Worker;

import android.util.Log;


/** A task managed by the WorkerManager. */
public class WorkerTask implements Runnable
{
	/** Our request requestHandle. */
	RequestHandle requestHandle;
	/** Our listener. */
	private WorkerTask.Listener listener;
	
	boolean cancelled = false;
	
	/** An exception that may occur during processing. */
	Throwable workerException = null;
	/** Our worker thread. Instance-level, so we can kill it from the <code>cancel()</code> method if needed. */
	Thread workerThread = null;
	/** True if the worker takes longer than the request timeout to do its work. */
	boolean timeoutError = false;
	/** The Request.Worker associated with this task. */
	private Request.Worker requestWorker;
	/** Data returned by the worker when it's done. */
	Object workerData = null;
	
	public WorkerTask(RequestHandle handle, WorkerTask.Listener listener)
	{
		super();
		this.requestHandle = handle;
		this.listener = listener;
	}
	
	/** Run this task for the specified <code>RequestHandle</code>. */
	public void run()
	{
		if(cancelled)
			return;
		
		if(requestHandle == null)
			throw new IllegalStateException("No requestHandle");
		
		// Get hold of the request and worker for this task.
		final Request request = requestHandle.getRequest();
		if(request == null)
			throw new IllegalStateException("No request associated with this request requestHandle");
		
		this.requestWorker = request.getWorker();
		if(this.requestWorker == null)
			throw new IllegalArgumentException("No worker associated with the request");
		
		try {
			this.workerException = null;
			this.timeoutError = false;
			
			this.workerThread = new Thread(new Runnable() {
				public void run()
				{
					try {
						requestWorker.work(new Request.Worker.Listener() {
							public void onData(Object data)
							{
								if(requestHandle.responseListener != null)
									requestHandle.responseListener.onDataReceived(request, data);
							}

							public boolean okToContinue(Worker worker)
							{
								return !cancelled;
							}
						});
					}
					catch(WorkerException ex)
					{
						// save this so someone can get to it.
						WorkerTask.this.workerException = ex;
					}
				}
			});
			
			this.workerThread.setPriority(Thread.MIN_PRIORITY);
			this.workerThread.start();
			
			// Wait on it to see if it times out.
			try {
				long waitTime = request.getTimeout();
				
				if(waitTime == Request.TIMEOUT_INFINITE)
				{
					// Don't just join() infinitely, since we'll need to check for cancellation.
					while(workerThread.isAlive())
					{
						try {
							workerThread.join(1000L);
							Thread.yield();
						}
						catch(InterruptedException ex)
						{
							this.workerException = ex;
							return;
						}
					}
				}
				else
				{
					workerThread.join(requestHandle.getRequest().getTimeout());
				}
				
				// It may have had an exception 
				if(this.workerException != null)
					return;
			}
			catch(InterruptedException ex)
			{
				this.workerException = ex;
				return;
			}
			
			// If the worker thread isn't done processing after we waited on it, it's a timeout.
			if(workerThread.isAlive())
			{
				this.timeoutError = true;
				
				workerThread.interrupt();
				return;
			}
			
			// All good. Get the data from the worker.
			this.workerData = requestWorker.getData();
		}
		finally {
			listener.onTaskComplete(this);
		}
	}
	
	/** Cancel this task. */
	public synchronized WorkerTask cancel()
	{
		Log.d("WorkerTask", "cancel()");
		
		try {
			this.cancelled = true;
			if(this.workerThread != null && this.workerThread.isAlive())
			{
				try {
					for(int i = 0; i < 10; ++i)
					{
						try {
							// Give it a second to die
							try {
								this.workerThread.join(200L);
							}
							catch(InterruptedException ex) {}
							
							if(!this.workerThread.isAlive())
								break;
							
							this.workerThread.interrupt();
						}
						catch(Exception ex)
						{
							Log.e("WorkerTask", "Interrupt " + this.workerThread.getName() + ": " + ex.toString());
						}
						
						if(this.workerThread.isAlive())
							Log.d("WorkerTask", "Worker is still alive.");
						
						this.cancelled = true;
						Thread.currentThread().interrupt();
					}
				}
				catch(Exception ex) {}
			}
		}
		finally {
			this.listener.onTaskComplete(this);
		}
		
		return this;
	}
	
	/** A listener to this task. */
	public static interface Listener 
	{
		void onTaskComplete(WorkerTask task);
	}
}
